/*
 * Copyright 2002-2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      https://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.fjsei.yewu.flow;

import com.querydsl.core.BooleanBuilder;
import graphql.relay.Connection;
import graphql.schema.DataFetchingEnvironment;
import io.camunda.zeebe.client.ZeebeClient;
import lombok.extern.slf4j.Slf4j;
import md.cm.base.Companies;
import md.cm.flow.ApprovalStmRepository;
import md.cm.geography.Adminunit;
import md.cm.unit.Unit;
import md.cm.unit.Units;
import md.log.Opinion_Enum;
import md.specialEqp.BusinessCat_Enum;
import md.specialEqp.Eqp;
import md.specialEqp.ReportRepository;
import md.specialEqp.inspect.*;
import md.system.*;
import org.fjsei.yewu.aop.MetricsLogger;
import org.fjsei.yewu.bpm.AskForApprovalService;
import org.fjsei.yewu.bpm.IncidentProcessListService;
import org.fjsei.yewu.exception.CommonGraphQLException;
import org.fjsei.yewu.graphql.DbPageConnection;
import org.fjsei.yewu.graphql.IdMapper;
import org.fjsei.yewu.input.AgreementInput;
import org.fjsei.yewu.jpa.PageOffsetFirst;
import org.fjsei.yewu.payload.AgreementCudResp;
import org.fjsei.yewu.resolver.Comngrql;
import org.fjsei.yewu.util.Tool;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.Sort;
import org.springframework.graphql.data.method.annotation.Argument;
import org.springframework.graphql.data.method.annotation.MutationMapping;
import org.springframework.graphql.data.method.annotation.QueryMapping;
import org.springframework.graphql.execution.BatchLoaderRegistry;
import org.springframework.stereotype.Controller;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;

import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import static graphql.Assert.assertNotNull;
import static graphql.Assert.assertTrue;

/**默认Unit模型的属性； graphQL接口;
 * 底下批处理方案还不是一种的。 注释掉的也是1个方案。
 * 每一个graphQL的Object对象type都需单独声明一个XxxController implements IdMapper<type> {} 否则Id都无法转换成Relay要的GlobalID的。
 * */
@Slf4j
@ConditionalOnBean(ZeebeClient.class)
@Controller
public class AgreementController extends Comngrql implements IdMapper<Agreement> {
	@PersistenceContext(unitName = "entityManagerFactorySei")
	private EntityManager emSei;                //EntityManager相当于hibernate.Session：
	private final Units units;
	private final Companies companies;
	private final TaskRepository tasks;
	private final IspRepository isps;
	private final ApprovalStmRepository  approvalStms;
	private final ReportRepository  reports;
	private final DetailRepository  details;
	private final Agreements agreements;
	private final AskForApprovalService askForApprovalService;
	private final IncidentProcessListService incidentProcessListService;

	/** registry参数：IDEA报红却能运行成功，实际运行注入DefaultBatchLoaderRegistry;
     *  消除报红，测试平台Units没有报红 https://blog.csdn.net/zy103118/article/details/84523452
	 * 添加了 @ComponentScan({"md","org.fjsei.yewu"}) 以后: Units报红消除了。
	 * */
	public AgreementController(BatchLoaderRegistry registry, Units units, Companies companies, TaskRepository tasks, IspRepository isps, ApprovalStmRepository approvalStms,
							   ReportRepository reports, DetailRepository details, Agreements agreements, AskForApprovalService askForApprovalService,
							   IncidentProcessListService incidentProcessListService) {
		this.units = units;
		this.companies = companies;
		this.tasks = tasks;
		this.isps = isps;
		this.approvalStms = approvalStms;
		this.reports = reports;
		this.details = details;
		this.agreements = agreements;
		this.askForApprovalService = askForApprovalService;
		this.incidentProcessListService = incidentProcessListService;
	}
	/**协议生成任务，前端协议 最终负责人可以自定义设备列表。
	 * 生成任务 ipsu检验机构默认user所属单位。 String servuId,对应@Autowired  Unit unit,
	 * 尝试失败：无法手动id加载实体！也就是函数参数用@Autowired  Unit servu=没法用的。
	 * date,bsType,entrust,servuId前面这四个接口参数是必须提供的；后面4个参数可选输入的。
	 * @param agreId ：是关联协议。 不能用String[] devs来接收的。
	 * */
	@MutationMapping
	@Transactional
	public  Task agreementAddTask(@Argument String agreId,@Argument LocalDate date, @Argument BusinessCat_Enum bsType, @Argument Boolean entrust,
						   @Argument List<String> devs) {
		User user=checkAuth();
		Assert.isTrue(null!=user, "未授权");
		Agreement agreement=entityOf(agreId,Agreement.class);
		assertNotNull(agreement, () -> "没找到协议");
		assertTrue(user.equals(agreement.getDispatcher()), () -> "最后责任人才能做");  	//前端告警清晰
		//只能在审核计费阶段才能生成协议的新任务； ?修改，删除，合并？若设备无关的业务：没法严格控制：在旧任务下修改业务Detail报告，增加Detail??。
		Assert.isTrue(ProtocolSta_Enum.CHECK == agreement.getStatus() ,"协议状态有问题");
		//不能简单地用Unit servUnit =entityOf(servuId,Unit.class);
		Unit servUnit=agreement.getServu();
		//【注意】servuId代表的PersonES有可能不存在对应的Unit啊，有些Person仅为了注册User平台账户而增加，不算在特种设备单位概念的之中。
		Assert.isTrue(servUnit != null, "未找到serv Unit");
		Task task = new Task();
		task.setDate(date);
		task.setBsType(bsType);
		task.setEntrust(null != entrust && entrust);
		task.setServu(servUnit);
		task.setIspu(user.getUnit());    //本单位的任务 "福建省特种设备检验研究院"
//		User liabler=entityOf(liablerId,User.class); =：协议最终负责人的部门和科室。
		task.setLiabler(agreement.getDispatcher());
		task.setOffice(agreement.getDispatcher().getOffice());
		task.setDep(agreement.getDispatcher().getDep());
		//直接关联设备多个；
		if(null!=devs) {         //Java比起JS啰嗦了！
			for (String devId : devs) {
				Eqp eqp = entityOf(devId, Eqp.class);
				if (null == eqp) throw new CommonGraphQLException("未找到Eqp:" + devId, devId);
				Isp isp = new Isp();
				isp.setDev(eqp);
				Detail detail = new Detail();
				//若光用detail.setIsp(isp);就无法实际关联上的！
				isp.setBus(detail);      //1:1关系维护方是Isp表。
				detail.setIsp(isp);      //若省略这行就导致：return task{无法内省出来.detail.isp==null}
				detail.setTask(task);
				task.getDets().add(detail);
				isps.save(isp);
				details.save(detail);
				//【立刻更新需要】在前端上无法立刻更新，看不见新生成任务；加底下2行点刷新URL可立刻看见。
				//eQP.getIsps().add(isp);
				//eQPRepository.save(eQP);
			}
		}
		task.setAgreement(agreement);
		//Set<Isp> isps= new HashSet<>();   List<Isp> isps=new ArrayList<>();
		task.setStatus(TaskState_Enum.INIT);      //协议还未生效 可能算作 临时任务状态。
		task.setCrman(user);
		task.setCrtime(LocalDateTime.now());
		tasks.save(task);
		return task;
	}


	/** 为任务添加设备
	 不能用ID类型javax.persistence@Id (, Id devId) Cannot deserialize Class jakarta.persistence.Id (of type annotation) as a Bean
	 * */
//	@MutationMapping
	@Transactional
	public Task addDeviceToTask(@Argument("task") String taskId, @Argument("dev") String devId,@Argument String repNo) {
		//分布式事务机制 if(!emSei.isJoinedToTransaction())      emSei.joinTransaction();
		Task task=entityOf(taskId,Task.class);
		Assert.isTrue(task != null, "未找到Task:" + taskId);
		Eqp eqp=entityOf(devId,Eqp.class);
		Assert.isTrue(eqp != null, "未找到Eqp:" + devId);
		Isp isp = new Isp();
		isp.setDev(eqp);
		isp.setNo(repNo);	 	//报告编码序号让前端和客户自己敲定，后端唯一性检查即可
		Detail detail = new Detail();
		isp.setBus(detail);
		detail.setIsp(isp);
		detail.setTask(task);
		task.getDets().add(detail);      //最早模型设想 task.getDevs().add(eQP);
		isps.save(isp);
		details.save(detail);
		tasks.save(task);
		return task;
	}

	/**终结整个任务。
	 * */
//	@MutationMapping
	@Transactional
	public Task finishTask(@Argument String taskId)
	{
		User user=checkAuth();
		Task task=entityOf(taskId,Task.class);
		Assert.isTrue(task != null,"未找到task:"+taskId);
		Assert.isTrue(user==task.getLiabler(),"非责任本人");
		Assert.isTrue(TaskState_Enum.DISP == task.getStatus(),"任务状态错");
		//Task必然最少一条Detail=1个检验Isp{report...}
		task.getDets().forEach(bus -> {
			bus.getIsp().getReps().forEach(report -> {
				Assert.isTrue(Procedure_Enum.END.equals(report.getStm().getSta()) || Procedure_Enum.CANCEL.equals(report.getStm().getSta()),"流程未终结");
			});
			Eqp eqp=bus.getIsp().getDev();
			String which= null==eqp? bus.getIdent() : StringUtils.hasText(eqp.getCod())? eqp.getCod(): eqp.getOid();
			Assert.isTrue(bus.isFeeOk(),"请确认收费:"+which);
		});
		//todo：【发票额以及实际回款的金额】必须大于等于 task.getCharge(); 放在哪一环节控制的, 关联委托协议合同或法定收费？
		Assert.isTrue(task.isFeeOk(),"请确认任务收费完成,预期实际收费:"+task.getCharge()+"元");
		task.setStatus(TaskState_Enum.DONE);
		tasks.save(task);
		return task;
	}

	/**前端手动对协议底下的某个Task:增加设备到该关联任务。
	 *报错：HibernateException: Unable to perform beforeTransactionCompletion callback: HSEARCH800023: Unable to process entities for automatic indexing before transaction completion
	 *报错因为hibernateSearchVersion = 6.2.0.Alpha2之后id变成varchar(255)类型了，适应通过物理数据库的UUID类型，hsearch_outbox_event hsearch_agent两个表删除！
	 * */
	@MutationMapping
	@Transactional
	public Agreement cudEqpToAgreementTask(@Argument("agreement") String agId, @Argument Ifop_Enu opt,@Argument("devs") List<String> devIds, @Argument("task") String taskId) {
		User user=checkAuth();
		assertNotNull(user, () -> "请登录");
		Agreement agreement=entityOf(agId,Agreement.class);
		assertNotNull(taskId, () -> "task未指定");
		Task task=entityOf(taskId,Task.class);
		assertNotNull(task, () -> "没任务");
		assertTrue(agreement==task.getAgreement(), () -> "非本协议附属任务");
		List<Eqp> devsTodo = new ArrayList<>();
		devIds.forEach(item -> {
			Eqp eqp=entityOf(item,Eqp.class);
//			assertTrue(task.getDets().contains(eqp), () -> "没有该项检验"+item);
			devsTodo.add(eqp);
		});
//		assertTrue(!detsTodo.isEmpty(), () -> "没待处理业务单");
		//给当前选定协议关联任务=task，增加设备：
		if(Ifop_Enu.ADD.equals(opt)) {
			assertTrue(TaskState_Enum.INIT.equals(task.getStatus()) || TaskState_Enum.DEPART.equals(task.getStatus()) || TaskState_Enum.OFFICE.equals(task.getStatus()), () -> "任务状态不支持");
			//task中已经存在的有效Eqp的有效的Detail检验需排除外！
			devsTodo.forEach(item -> {
				boolean existOld=task.getDets().stream().anyMatch(det->{
					return	(null!=det.getIsp().getDev() && det.getIsp().getDev().equals(item));
				});
				if(!existOld) {
					Isp isp = new Isp();
					isp.setDev(item);
					Detail detail = new Detail();
					//若光用detail.setIsp(isp);就无法实际关联上的！
					isp.setBus(detail);      //1:1关系维护方是Isp表。
					detail.setIsp(isp);      //若省略这行就导致：return task{无法内省出来.detail.isp==null}
					detail.setTask(task);
					task.getDets().add(detail);
					isps.save(isp);
					details.save(detail);
				}
			});
			tasks.save(task);
//			task.getDets().removeAll(detsTodo);	 	//需要 ？当前接口就刷新。
		}
		return agreement;
	}

	/**协议申请单的新建，修改，删除 :复合模式接口。
	 * 申请单提交也是用Ifop_Enu:UPD,
	 * 【zeebe终端被挂住】导致这里连续多次点击的，可能导致产生多个的流程实例。@Transactional也无法阻挡zeebe提交啊，DB互斥失败回滚zeebe也不能退回。
	 * */
	@MutationMapping
	@Transactional
	public AgreementCudResp cudAgreement(@Argument String id, @Argument Ifop_Enu opt, @Argument AgreementInput inp) {
		AgreementCudResp resp=new AgreementCudResp();
		User user=checkAuth();
		assertNotNull(user, () -> "请登录");
		Agreement agreement=entityOf(id,Agreement.class);
		if(!Ifop_Enu.ADD.equals(opt)) {
			assertNotNull(agreement, () -> "没协议申请单");
			if (ProtocolSta_Enum.SIGNED == agreement.getStatus()) {
				assertTrue(user.equals(agreement.getDispatcher()), () -> "责任人才能做");
				if(ProtocolSta_Enum.END==inp.getStatus()){
			//todo 结束协议
				}else if(ProtocolSta_Enum.CANCEL==inp.getStatus()){
			//todo 取消协议
				}
			}
		}
		List<Eqp> detsTodo = new ArrayList<>();
		if(!Ifop_Enu.DEL.equals(opt) && null!=inp.getDevs())
			inp.getDevs().forEach(item -> {
				Eqp eqp=entityOf(item,Eqp.class);
				assertNotNull(eqp, () -> "没该设备"+item);
				detsTodo.add(eqp);
			});
//		assertTrue(!detsTodo.isEmpty(), () -> "没待处理业务单");
		Unit servu=fromInputUnitGlobalID(inp.getServu());
		Unit ispu=fromInputUnitGlobalID(inp.getIspu());
		User promoter=entityOf(inp.getPromoter(),User.class);
		User auditor=entityOf(inp.getAuditor(),User.class);
		User dispatcher=entityOf(inp.getDispatcher(),User.class);
		Adminunit adminunit=entityOf(inp.getAd(),Adminunit.class);
		Agreement.AgreementBuilder  agreementBld=null;
		if(Ifop_Enu.UPD.equals(opt))
			agreementBld= agreement.toBuilder();
		else if(Ifop_Enu.ADD.equals(opt))
			agreementBld= Agreement.builder();
		if(!Ifop_Enu.DEL.equals(opt)) {
			//新增时 固定完成的servu，pttype不能被Update修改掉! , 但ptno可以再改。
			assert agreementBld != null;
			agreementBld = agreementBld.ptno(inp.getPtno())
					.entrust(inp.getEntrust()).bsType(inp.getBsType()).ad(adminunit)
					.transferor(inp.getTransferor()).complDate(inp.getComplDate()).charge(inp.getCharge())
					.mdtime(LocalDateTime.now()).aux(inp.getAux())
					.devs(detsTodo);
			//只能修改，不能清空旧的设置的；
			agreementBld =null!=auditor? agreementBld.auditor(auditor) : agreementBld;
			agreementBld =null!=dispatcher? agreementBld.dispatcher(dispatcher) : agreementBld;
			agreementBld =null!=promoter? agreementBld.promoter(promoter) : agreementBld;
		}
		if(Ifop_Enu.ADD.equals(opt)) {
//			assertTrue(TaskState_Enum.INIT.equals(task.getStatus()) || TaskState_Enum.DEPART.equals(task.getStatus()) || TaskState_Enum.OFFICE.equals(task.getStatus()), () -> "状态不支持拆分");
			agreement= agreementBld.crman(user).ispu(ispu).servu(servu).pttype(inp.getPttype()).build();
//			Task finalNewTask = newTask;
//			detsTodo.forEach(item -> {
//				item.setTask(finalNewTask);
//			});
			agreements.save(agreement);
//			task.getDets().removeAll(detsTodo);	 	//需要 ？当前接口就刷新。
		}
		else if(Ifop_Enu.UPD.equals(opt)) {
			//检查各阶段修改授权：
			if (ProtocolSta_Enum.INIT == agreement.getStatus())
				assertTrue(user.equals(agreement.getCrman()), () -> "本人才能做");
			else if(ProtocolSta_Enum.SUBMIT == agreement.getStatus())
				assertTrue(user.equals(agreement.getCrman()) || user.equals(agreement.getPromoter()), () -> "非授权节点");
			else if(ProtocolSta_Enum.CHECK == agreement.getStatus())
				assertTrue(user.equals(agreement.getAuditor()) || user.equals(agreement.getDispatcher()), () -> "非授权节点");
			else if(ProtocolSta_Enum.SIGNING == agreement.getStatus())
				throw new CommonGraphQLException("签名中不能改", id);
			assertTrue(ProtocolSta_Enum.SIGNED != agreement.getStatus(), () -> "已签订不能改");
			assertTrue(ProtocolSta_Enum.END != agreement.getStatus(), () -> "已结束不能改");
				//throw new CommonGraphQLException("已结束不能改", id); 不如上面简洁;

			if(ProtocolSta_Enum.SUBMIT==inp.getStatus()) {		//前端"提交申请"命令也走这个接口!
				if (null == agreement.getStatus() || ProtocolSta_Enum.INIT == agreement.getStatus()) {
					assertTrue(null == agreement.getPrId(), () -> "流程id非空！");
					List<User> listCkr= findUserByUnitAndAuthority(agreement.getIspu(), AuthorityName_Enum.Sponsor);
//					List<User> listCkr=new ArrayList<>();
//					listCkr.add(listCkr0.get(1)); 	//测试看 todo://
//					listCkr.add(listCkr0.get(0));
					String  enId=agreement.getId(null);
					//【毛病】本来应该前端输入的：？？  todo:临时
					String  uri="/agreement/"+enId+"/"+agreement.getPttype()+"/flowSelect";
					Long processId = askForApprovalService.startFlow("AgreementPrepare",agreement.getServu().getName(null),enId,uri,listCkr);
					//关键的Process Instance Id，@@要避免遗忘掉已经生成的流程！zeebe引擎存储的遗漏占用;
					assertTrue(null != processId, () -> "流程开启失败");
					agreementBld.prId(processId).status(ProtocolSta_Enum.SUBMIT);
				}
				else resp.setWarn("申请单早已提交");
				//作废已经生效的协议？需要另起炉灶还是直接替换？最好Agreement实体新建但是任务报告可以重新关联，首先断开旧协议关联，替换关联ID。
			}
			agreement= agreementBld.build();
			//校验划转任务的任务容受性。同一个受检验单位？ JPA实体代理?不能直接比较？ assertTrue(newTask.getServu().equals(task.getServu())会报错！
//			assertTrue(newTask.getServu().getId().equals(task.getServu().getId()), () -> "非同一受检验单位");
//			Task finalNewTask1 = newTask;
//			detsTodo.forEach(item -> {
//				item.setTask(finalNewTask1);
//			});
//			newTask.getDets().addAll(detsTodo);
//			task.getDets().removeAll(detsTodo);
			agreements.save(agreement);
		}
		else {		 //if(Ifop_Enu.DEL.equals(opt))   删除接口！
			//权限检查 or 审批？ 特别地放入日志。 todo？提交流转后直接作废？未提交的主动早早删除的。
//            INIT : 申请创建者有权删除。
//            END  ：没有人有权删除，只能后台维护删除。协议要删除的？有权限取消协议附属的任务？(协议取消还是已经协议完成执行终结的，不能随意删计费依据)
//            CANCEL ：申请创建者||最后责任人有权删除。
//            SUBMIT或其它状态：：其它状态都不能删除, 需要流程推进到CANCEL后再考虑删除。
			if(ProtocolSta_Enum.INIT == agreement.getStatus()){
                assertTrue(user.equals(agreement.getCrman()), () -> "创建者才能做");
            }
            else if(ProtocolSta_Enum.CANCEL == agreement.getStatus()){
                assertTrue(user.equals(agreement.getDispatcher()) || user.equals(agreement.getCrman()), () -> "责任人或创建者才能做");
				//申请创建者: 若是客户本人的，没有权做取消协议已关联任务的，实际可能无法成功删除协议。
            }else
				throw new CommonGraphQLException("没权删除或状态不允许", id);

            if(null!=agreement.getPrId()){
				if(!incidentProcessListService.canDeleteProcessInstance(agreement.getPrId()))
					throw new CommonGraphQLException("流程状态不允许删", id);
            }

//			List<Isp>  ispsTodo = new ArrayList<>();
//			detsTodo.forEach(det -> {
//				if(null==det.getIsp())	 return;
//				assertTrue(det.getIsp().getReps().isEmpty(), () -> "关联报告未清除,detId:"+det.getId());
//				ispsTodo.add(det.getIsp());	  	//永久库清理
//			});
//			isps.deleteAll(ispsTodo);
//			details.deleteAll(detsTodo);
//			task.getDets().removeAll(detsTodo);

			//直接删除报错：foreign key constraint: is still referenced from table 'task'; @需要是事前确保没有被别人关联到: cancellationTask接口可删。谁有权删除?
			agreements.delete(agreement);
		}
		resp.setMe(agreement);
		return resp;
	}
	/**协议申请单/协议 查询：
	 * ?从User/me.tasks[]#node().再去关联内省更好，没必要整出独立的graphQL查询接口。
	 * 部门任务分配角色人:分给底下科室或者转移其它部门，科室任务分配角色人:分给底下任务责任人或者转移其它科室，责任人个人：确保任务不会超期要派工召集人员干活或者退回Task到科室中。
	 *前端揭示：我部门任务， 我科室任务， 责任人关注任务：前端管的{状态{7个enum/or常见组合}过滤[]}+时间排序?,服务对象单位过滤,时间区间[]bsType entrust, 整个Task/IspUnit单位的所有任务全列表在后台维护{任务超期了}。
	 * 【不会报错】这里定义@Argument TaskInput where, 和模型文件中定义类型AgreementInput 不匹配的也能跑啊。
	 * */
	@MetricsLogger
	@QueryMapping
	public Connection<Agreement> findAgreementFilter(@Argument String orderBy, @Argument Boolean asc, @Argument AgreementInput where, @Argument Integer first,
											  @Argument String after, @Argument Integer last, @Argument String before, DataFetchingEnvironment env) {
		User user=checkAuth();
		assertNotNull(user, () -> "请登录");
		DbPageConnection<Agreement> connection=new DbPageConnection(env);
		int offset=connection.getOffset();
		int limit=connection.getLimit();
		//【问题】这里排序date排序ASC后的，若没有添加id做辅助排序字段，导致多次执行从数据库读的分页数据很可能是重复的，CRDB返回实际记录的顺序不一定固定。不唯一的排序组合问题。
		//前端Relay列表看出：有些loadMore()可能并没有增加新实体，重复被自动归并了,导致队列长度也没增加；浪费通信数据。
		Pageable pageable;
		if (!StringUtils.hasLength(orderBy))
			pageable = PageOffsetFirst.of(offset, limit);
		else {
			pageable = PageOffsetFirst.of(offset, limit, Sort.by(asc ? Sort.Direction.ASC : Sort.Direction.DESC, orderBy, "id"));
		}
		QAgreement qm = QAgreement.agreement;
		BooleanBuilder builder = new BooleanBuilder();
		//部门 科室针对的是 最后责任人：
		if(StringUtils.hasText(where.getDep()))
			builder.and(qm.dispatcher.dep.id.eq(Tool.fromGluuId(where.getDep()).getId()));
		if(StringUtils.hasText(where.getOffice()))
			builder.and(qm.dispatcher.office.id.eq(Tool.fromGluuId(where.getOffice()).getId()));
		if(StringUtils.hasText(where.getDispatcher()))
			builder.and(qm.dispatcher.id.eq(Tool.fromGluuId(where.getDispatcher()).getId()));
		if(null!=where.getDate1())
			builder.and(qm.complDate.goe(where.getDate1()));
		if(null!=where.getDate2())
			builder.and(qm.complDate.loe(where.getDate2()));
		if(null!=where.getQdate1())
			builder.and(qm.mdtime.goe(where.getQdate1().atStartOfDay()));
		if(null!=where.getQdate2())
			builder.and(qm.mdtime.loe(where.getQdate2().plusDays(1).atStartOfDay()));	   //0点（不包含这一天）
		if(null!=where.getStatusx() && where.getStatusx().size()>0)
			builder.and(qm.status.in(where.getStatusx()));
        if(StringUtils.hasText(where.getPttype()))
			builder.and(qm.pttype.eq(where.getPttype()));
        if(StringUtils.hasText(where.getPtno()))
			builder.and(qm.ptno.eq(where.getPtno()));
		if(StringUtils.hasText(where.getServu())) {
			Unit unit=fromInputUnitGlobalID(where.getServu());
			Assert.notNull(unit,"未找到Unit:"+where.getServu());
			builder.and(qm.servu.id.eq(unit.getId()));
		}
		if(StringUtils.hasText(where.getIspu())) {
			Unit unit=fromInputUnitGlobalID(where.getIspu());
			Assert.notNull(unit,"未找到Unit:"+where.getIspu());
			builder.and(qm.ispu.id.eq(unit.getId()));
		}
//		if(StringUtils.hasText(where.getCrman()))
//			builder.and(qm.crman.id.eq(Tool.fromGluuId(where.getCrman()).getId()));
		if(Boolean.TRUE.equals(where.getMecrman()))
			builder.and(qm.crman.id.eq(user.getId()));
		Slice<Agreement> rpage= (Slice<Agreement>)agreements.findAll(builder,pageable);
		List<Agreement> list=(List<Agreement>) rpage.toList();
		return connection.setListData(list).get(env);
	}
	/**处理zeebe服务的UserTask列表， 完成后当前用户 确认审批或者回退等操作或签字过关。
	 * 修改报告后重新再一次结束编制状态这时刻校核人员有权再次重新流转的也用这个接口,而不是全新未发生流程行为的flowReport()。
	 * graphQL接口方法是不可重复简易名字(类路径不同的也不行)。报错！Mutation.flowTo: There is already 'agreementController' bean method Mutation.flowTo -> public java.lang.String org.fjsei.yewu.flow.AgreementController.flowTo
	 *【考虑】本接口复合做法：流转同时前端选定责任人，【耦合】根据流程图节点认定输入用途，顺带指定一个任务负责人：不一定要求zeebe体现运行计算收费子流程，服务端需要任务负责人。
	 * 前端根据流程用户任务节点名字关系定位：合适指定任务责任人，通过接口附带属性区分到底接口选定的是什么人角色。
	 * 前端输入的userTaskId有可能并不是操作者的用户任务！必须验证身份。
	 *  */
	@MutationMapping
	@Transactional
	public String  applicationFlowTo(@Argument String entId, @Argument String userTaskId, @Argument Opinion_Enum allow, @Argument String memo,
						  @Argument String men, @Argument Integer days, @Argument String uri,@Argument Boolean calcfee) {
		User user=checkAuth();
		User nextMan = entityOf(men,User.class);	 //提交给谁处理:
		//Assert.isTrue(approver != null,"未找到user:"+men);
//		Tool.ResolvedGuuid gId =  Tool.fromGluuId(entId);
//		UUID uuid = gId.getId();
//		String entType = gId.getType();		任意可能实体模型名
		Agreement agreement=entityOf(entId,Agreement.class);
//		Report report = entityOf(entId,Report.class);   //或者其他的申请单实体 都 instance of FlowStat
		if(null==agreement)    return "没找到申请单";
		//??直接从zeebe服务提取 流程变量：统一流程变量ent 代表挂接的申请单或报告的实体表ID: GlobalID;
		//应当把关联的Report报告id给我传递进来啊，写入流程日志实体表也得用它。普通申请单的实体模型对象也有ID。
		io.camunda.tasklist.dto.Task userTask= askForApprovalService.getFlownode(userTaskId);
		if(!user.getUsername().equals(userTask.getAssignee()))
			return "不是自己的待办";	 	 //竟然可以随意替别人流转。 @ zeebe安全接入??
		String flnode=userTask.getTaskDefinitionId();
		if("agreement_reviewer_check".equals(flnode)){
			agreement.setDispatcher(nextMan);	   //可能会回退2个流方向。
		}
		else if("agreement_promoter_action".equals(flnode)){
			agreement.setAuditor(nextMan);
		}
		else nextMan=null;	  //zeebe流程引擎BPMN定义上没用到
		String rUserTaskId= askForApprovalService.flowTo(userTaskId, agreement, allow, memo,days, nextMan,uri,calcfee);
		if(null==rUserTaskId)	return "移交失败";
		agreements.save(agreement);
		return  rUserTaskId;
	}
}

